/* 
 * Depends: InitClass.js, Control.js
 * Purpose: provide a list supports
 */

NJS.List = NJS.Control.extend({
    _getDefaultOptions: function () {
        return {
            matchingStrategy: "(^|.*\\s+)({0})(.*)",
            containerSelector: ".aui-list",
            itemSelector: "li"
        }
    },
    index: 0,
    moveToNext: function () {
        if (this.index < this.maxIndex) {
            this.unfocusAll();
            ++this.index;
            this.focus(this.SCROLL_DOWN)
        } else {
            if (this.$visibleItems.length > 1) {
                this.unfocusAll();
                this.index = 0;
                this.focus(this.SCROLL_DOWN)
            }
        }
        this.motionDetector.wait()
    },
    SCROLL_UP: -1,
    SCROLL_DOWN: 1,
    container: function (container) {
        if (container) {
            this.$container = $(container);
            this.containerSelector = container
        } else {
            return this.$container
        }
    },
    scrollContainer: function () {
        return this.container().parent()
    },
    unfocusAll: function () {
        this.$visibleItems.removeClass("active")
    },
    moveToPrevious: function () {
        if (this.index > 0) {
            this.unfocusAll();
            --this.index;
            this.focus(this.SCROLL_UP)
        } else {
            if (this.$visibleItems.length > 0) {
                this.unfocusAll();
                this.index = this.$visibleItems.length - 1;
                this.focus(this.SCROLL_UP)
            }
        }
        this.motionDetector.wait()
    },
    unfocus: function (direction) {
        if (direction !== undefined) {
            this.scrollTo(this.$visibleItems.eq(this.index), direction)
        }
        this.$visibleItems.eq(this.index).removeClass("active")
    },
    scrollTo: function ($target, direction) {
        var $scrollContainer = this.scrollContainer(),
            offsetTop = $target.offset().top - this.$container.offset().top;
        if ($target[0] === this.$visibleItems[0]) {
            $scrollContainer.scrollTop(0)
        } else {
            if ($scrollContainer.scrollTop() + $scrollContainer.height() < offsetTop + $target.outerHeight() || $scrollContainer.scrollTop() > offsetTop) {
                if (direction === -1) {
                    $scrollContainer.scrollTop(offsetTop)
                } else {
                    if (direction === 1) {
                        $scrollContainer.scrollTop(offsetTop + $target.outerHeight() - $scrollContainer.height())
                    }
                }
            }
        }
    },
    focus: function (direction) {
        var $target = this.$visibleItems.eq(this.index);
        if (direction !== undefined) {
            this.scrollTo($target, direction)
        }
        this.lastFocusedItemDescriptor = $target.data("descriptor");
        this.motionDetector.unbind();
        $target.addClass("active");
        if (!NJS.dim.dim) {
            $target.scrollIntoView({
                duration: 100,
                callback: $.proxy(this.motionDetector, "wait")
            })
        } else {
            this.motionDetector.wait()
        }
    },
    motionDetector: new JIRA.Mouse.MotionDetector(),
    disable: function () {
        if (this.disabled) {
            return
        }
        this._unassignEvents("document", document);
        this.disabled = true;
        this.lastFocusedItemDescriptor = null;
        this.motionDetector.unbind()
    },
    enable: function () {
        var instance = this;
        if (!instance.disabled) {
            return
        }
        instance.motionDetector.wait();
        window.setTimeout(function () {
            instance._assignEvents("document", document)
        }, 0);
        instance.disabled = false;
        this.scrollContainer().scrollTop(0)
    },
    getFocused: function () {
        return this.$visibleItems.filter(".active")
    },
    reset: function (index) {
        var noSuggestionsClassName = /(?:^|\s)no-suggestions(?!\S)/;
        var hiddenClassName = /(?:^|\s)hidden(?!\S)/;
        this.$container = $(this.options.containerSelector);
        this.items = $(this.options.itemSelector, this.$container).filter(function () {
            return !noSuggestionsClassName.test(this.className)
        });
        this.$visibleItems = this.items.filter(function () {
            return !hiddenClassName.test(this.className)
        });
        this.groups = $(this.options.groupSelector, this.$container);
        this.maxIndex = this.$visibleItems.length - 1;
        this.index = this.$visibleItems[index] ? index : 0;
        this.focus()
    },
    init: function (options) {
        options = options || {};
        if (options) {
            this.options = $.extend(true, this._getDefaultOptions(options), options)
        } else {
            this.options = this._getDefaultOptions(options)
        }
        var instance = this;
        this.containerSelector = $(this.options.$layerContent);
        this.disabled = true;
        this.reset();
        if (this.options.selectionHandler) {
            this.$container.delegate(this.options.itemSelector, "click", function (e) {
                instance.options.selectionHandler.call(instance, e)
            })
        }
        this.$container.delegate(this.options.itemSelector, "mouseover", function () {
            if (instance.motionDetector.moved && !instance.disabled) {
                instance.unfocusAll();
                instance.index = $.inArray(this, instance.$visibleItems);
                instance.focus()
            }
        })
    },
    _getLinkFromItem: function (item) {
        var link;
        item = $(item);
        if (item.is("a")) {
            link = item
        } else {
            link = item.find("a")
        }
        if (!link.length) {
            throw new Error("NJS.List._getLinkFromItem: could not find a link node")
        } else {
            return link
        }
    },
    generateListFromJSON: function (data, query) {
        var event, $result = $("<div>"),
            instance = this,
            ungrouped = [],
            $listItems;
        this.suggestions = 0;
        this.exactMatchIndex = -1;
        this.lastFocusedIndex = -1;
        this.lastQuery = query;
        $.each(data, function (i, descriptor) {
            if (descriptor instanceof NJS.GroupDescriptor) {
                if (ungrouped.length > 0) {
                    $result.append(instance._generateUngroupedOptions(ungrouped, query));
                    ungrouped = []
                }
                $result.append(instance._generateOptGroup(descriptor, query))
            } else {
                if (this instanceof NJS.ItemDescriptor) {
                    ungrouped.push(descriptor)
                }
            }
        });
        if (ungrouped.length > 0) {
            $result.append(this._generateUngroupedOptions(ungrouped, query))
        }
        if ($result.children().length === 0) {
            this.$container.html(this._render("noSuggestion"))
        } else {
            $result.find("ul:last").addClass("aui-last");
            this.$container.html($result.children())
        }
        this.$container.hide();
        $listItems = $("li > a", this.$container);
        $listItems.each(function () {
            var elem = $(this);
            elem.attr("title", elem.text())
        });
        $listItems.css({
            textOverflow: "ellipsis",
            overflow: "hidden"
        });
        this.$container.show();
        $listItems.textOverflow("&#x2026;", false);
        NJS.trigger("contentChange", this.$container);
        this.reset(this.exactMatchIndex >= 0 ? this.exactMatchIndex : this.lastFocusedIndex)
    },
    _generateOption: function (item, query) {
        var replacementText;
        if (query) {
            var regexEscapedQuery = RegExp.escape(query),
                regex = new RegExp(NJS.format(this.options.matchingStrategy, regexEscapedQuery), "i");
            if (!regex.test(item.label())) {
                return null
            }
            replacementText = item.label().replace(regex, function (_, prefix, match, suffix) {
                return $("<div>").append($("<span>").text(prefix)).append($("<em>").text(match)).append($("<span>").text(suffix)).html()
            })
        }
        if (this.exactMatchIndex < 0) {
            var itemValue = $.trim(item.label()).toLowerCase();
            if (itemValue === $.trim(query).toLowerCase()) {
                this.exactMatchIndex = this.suggestions
            } else {
                if (this.lastFocusedIndex < 0 && this.lastFocusedItemDescriptor && itemValue === $.trim(this.lastFocusedItemDescriptor.label()).toLowerCase()) {
                    this.lastFocusedIndex = this.suggestions
                }
            }
        }
        this.suggestions++;
        return this._render("suggestion", item, replacementText)
    },
    _generateUngroupedOptions: function (options, query) {
        var hasSuggestion = false,
            instance = this,
            $container = this._render("ungroupedSuggestions");
        $.each(options, function (_, option) {
            var $suggestion = instance._generateOption(option, query);
            if ($suggestion) {
                hasSuggestion = true;
                $container.append($suggestion)
            }
        });
        if (hasSuggestion) {
            return $container
        }
    },
    _generateOptGroup: function (groupDescriptor, query) {
        var res = $(),
            hasSuggestion, instance = this,
            optContainer = this._render("suggestionGroup", groupDescriptor);
        $.each(groupDescriptor.items(), function (i, option) {
            var suggestion = instance._generateOption(option, query);
            if (suggestion) {
                hasSuggestion = true, optContainer.append(suggestion)
            }
        });
        if (!hasSuggestion) {
            return
        }
        if (groupDescriptor.showLabel() !== false) {
            res = res.add(this._render("suggestionGroupHeading", groupDescriptor))
        }
        res = res.add(optContainer);
        return res
    },
    _events: {
        document: {
            keydown: function (e) {
                this._handleKeyEvent(e)
            },
            keypress: function (e) {
                this._handleKeyEvent(e)
            }
        }
    },
    _renders: {
        suggestion: function (descriptor, replacementText) {
            var listElem = $('<li class="aui-list-item aui-list-item-' + $.trim(descriptor.label().toLowerCase()).replace(/[\s\.]+/g, "-") + '">'),
                linkElem = $("<a />").addClass("aui-list-item-link");
            if (descriptor.selected()) {
                listElem.addClass("aui-checked")
            }
            linkElem.attr("href", descriptor.href() || "#");
            if (descriptor.icon() && descriptor.icon() !== "none") {
                linkElem.addClass("aui-iconised-link").css({
                    backgroundImage: "url(" + descriptor.icon() + ")"
                })
            }
            if (descriptor.styleClass()) {
                linkElem.addClass(descriptor.styleClass())
            }
            if (descriptor.html()) {
                linkElem.html(descriptor.html())
            } else {
                if (!replacementText) {
                    linkElem.text(descriptor.label())
                } else {
                    linkElem.html(replacementText)
                }
            }
            if (descriptor.labelSuffix()) {
                $("<span class='aui-item-suffix' />").text(descriptor.labelSuffix()).appendTo(linkElem)
            }
            listElem.append(linkElem).data("descriptor", descriptor);
            return listElem
        },
        noSuggestion: function () {
            return $("<div class='no-suggestions'><span style='font-style:oblique'>" + NJS.params.frotherNomatches + "</span></div>")
        },
        ungroupedSuggestions: function () {
            return $("<ul>")
        },
        suggestionGroup: function (descriptor) {
            return $("<ul class='aui-list-section' />").attr("id", descriptor.label().replace(/\s/g, "-").toLowerCase()).addClass(descriptor.styleClass()).data("descriptor", descriptor)
        },
        suggestionGroupHeading: function (descriptor) {
            var elem = $("<h5 />").text(descriptor.label()).addClass(descriptor.styleClass()).data("descriptor", descriptor);
            if (descriptor.description()) {
                $("<span class='aui-section-description' />").text(" (" + descriptor.description() + ")").appendTo(elem)
            }
            return elem
        }
    },
    _acceptSuggestion: function (item) {
        if (!item instanceof $) {
            item = $(item)
        }
        var linkNode = this._getLinkFromItem(item);
        var event = new jQuery.Event("click");
        linkNode.trigger(event, [linkNode]);
        if (!event.isDefaultPrevented()) {
            window.location.href = linkNode.attr("href")
        }
    },
    _acceptUserInput: function ($field) {
        $field.triggerHandler("blur")
    },
    _handleSectionByKeyboard: function (e) {
        var $focusedItem = this.getFocused();
        var $field = $(e.target);
        if ($focusedItem.length === 0) {
            return
        }
        if ($focusedItem.closest("#user-inputted-option").length > 0) {
            this._acceptUserInput($field);
            return
        }
        if (this._latestQuery && $field.val() !== this._latestQuery) {
            var inputWords = $field.val().toLowerCase().match(/\S+/g);
            if (inputWords) {
                var html = this.lastFocusedItemDescriptor && this.lastFocusedItemDescriptor.html();
                var $item = html ? $("<div>").html(html) : $focusedItem;
                var matches = $.map($item.find("em,b"), function ($match) {
                    $match = $($match);
                    return ($match.text() + $($match.attr("nextSibling")).text().match(/^\S*/)[0]).toLowerCase()
                });
                for (var i = 0; i < inputWords.length; i++) {
                    var word = inputWords[i];
                    var n = word.length;
                    var hasMatch = false;
                    for (var j = 0; j < matches.length; j++) {
                        if (matches[j].slice(0, n) === word) {
                            hasMatch = true;
                            break
                        }
                    }
                    if (!hasMatch) {
                        this._acceptUserInput($field);
                        return
                    }
                }
            }
        }
        if (this.options.selectionHandler && !this.options.selectionHandler.call(this, e)) {
            return
        }
        this._acceptSuggestion($focusedItem)
    },
    _isValidInput: function () {
        return !this.disabled && this.$container.is(":visible")
    },
    keys: {
        down: function (e) {
            this.moveToNext();
            e.preventDefault()
        },
        up: function (e) {
            this.moveToPrevious();
            e.preventDefault()
        },
        "return": function (e) {
            this._handleSectionByKeyboard(e)
        }
    }
});
